﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Upreader.Usenet.Nntp.Encodings
{
    /// <summary>
    /// Represents a class which can automatically detect and decode the underlying encoding of the given basestream.
    /// </summary>
    public class EncodedStream : Stream
    {
        #region Configuration

        public static readonly IStreamDecoder[] DefaultStreamDecoders = new IStreamDecoder[]
        {
            new YencStreamDecoder(),
            new UUEncodeStreamDecoder()
        };

        #endregion // Configuration

        #region Fields

        private readonly Stream _baseStream;
        private Stream _decodeStream;
        private bool _isDisposed;
        private readonly IEnumerable<IStreamDecoder> _streamDecoders;
        private IStreamDecoder _availableDecoder;
        private Type _expectedDecoderType;

        #endregion // Fields

        #region Constructor

        public EncodedStream(Stream baseStream)
            : this(baseStream, DefaultStreamDecoders, null)
        {
        }

        public EncodedStream(Stream baseStream, IEnumerable<IStreamDecoder> streamDecoders, Type expectedDecoderType)
        {
            if (baseStream == null)
                throw new ArgumentNullException("baseStream");
            if (streamDecoders == null)
                throw new ArgumentNullException("streamDecoders");

            _baseStream = baseStream;
            _streamDecoders = streamDecoders;
            _expectedDecoderType = expectedDecoderType;
        }

        #endregion // Constructor

        #region Protected properties

        protected bool IsDisposed { get { return _isDisposed; } }

        #endregion // Protected properties

        #region Properties

        public Stream BaseStream { get { return _baseStream; } }

        /// <summary>
        /// Dependent on the BaseStream
        /// </summary>
        public override bool CanRead
        {
            get { return BaseStream.CanRead; }
        }

        /// <summary>
        /// Not supported, always false
        /// </summary>
        public override bool CanSeek
        {
            get { return false; }
        }

        /// <summary>
        /// Not supported, always false
        /// </summary>
        public override bool CanWrite
        {
            get { return false; }
        }

        /// <summary>
        /// Not supported, throws
        /// </summary>
        public override long Length
        {
            get { throw new NotSupportedException(); }
        }

        /// <summary>
        /// Not supported, throws
        /// </summary>
        public override long Position
        {
            get
            {
                throw new NotSupportedException();
            }
            set
            {
                throw new NotSupportedException();
            }
        }

        /// <summary>
        /// Get or set a set of optional decoders for the basestream
        /// </summary>
        public IEnumerable<IStreamDecoder> StreamDecoders
        {
            get { return _streamDecoders; }
        }

        /// <summary>
        /// Get the available decoder detected
        /// </summary>
        public IStreamDecoder AvailableDecoder
        {
            get 
            {
                return _availableDecoder;
            }
            protected set
            {
                _availableDecoder = value;

                EventHandler eventHandler = AvailableDecoderFound;
                if (eventHandler != null)
                    eventHandler(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Get the stream used for decoding
        /// </summary>
        public Stream DecodeStream
        {
            get { return _decodeStream; }
        }

        /// <summary>
        /// Get wether there is an available decoder detected
        /// </summary>
        public bool IsAvailableDecoderDetected
        {
            get { return _availableDecoder != null; }
        }

        #endregion // Properties

        #region Methods

        public override void Flush()
        {
            ValidateObjectState();
            BaseStream.Flush();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            ValidateObjectState();

            // find a decoder first
            if (!IsAvailableDecoderDetected)
            {
                FindAvailableDecoder();

                if (!IsAvailableDecoderDetected)
                {
                    throw new InvalidOperationException("Could not find a decoder which is valid for the stream given");
                }
            }

            int total = _decodeStream.Read(buffer, offset, count);

            return total;
        }

        /// <summary>
        /// not supported, always throws
        /// </summary>
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Not supported, throws
        /// </summary>
        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// not supported, always throws
        /// </summary>
        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Closes the current and underlying stream
        /// </summary>
        public override void Close()
        {
            ValidateObjectState();
            BaseStream.Close();
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            _isDisposed = true;
        }

        #endregion // Methods

        #region Helpers

        protected void ValidateObjectState()
        {
            if (_isDisposed)
                throw new InvalidOperationException(Resource.ObjectDisposed);
        }

        protected void FindAvailableDecoder()
        {
            // create a stream for recording the data coming from basestream in order to test against multiple decoders
            RecordStream recordStream = new RecordStream(_baseStream);
            IStreamDecoder availableDecoder = null;
            Func<Stream, Stream> decodeStreamFactory = null;

            // first try and quess the expected decoder if input was given
            IStreamDecoder expectedDecoder = StreamDecoders.FirstOrDefault(x => x.GetType() == _expectedDecoderType);
            if (expectedDecoder != null)
            {
                if (expectedDecoder.TryCreateDecodeStreamFactory(recordStream, out decodeStreamFactory))
                {
                    availableDecoder = expectedDecoder;
                }
            }


            foreach (IStreamDecoder streamDecoder in StreamDecoders)
            {
                // Put the record stream in playback mode for rereading any previously red data and storing any temporary data
                recordStream.Playback();

                if (streamDecoder.TryCreateDecodeStreamFactory(recordStream, out decodeStreamFactory))
                {
                    availableDecoder = expectedDecoder;
                    break;
                }
            }

            // put the record stream into only play back mode, dont record any new data
            recordStream.Playback(false);

            if (availableDecoder != null)
            {
                _decodeStream = decodeStreamFactory(recordStream);
                AvailableDecoder = availableDecoder;
            }
        }

        #endregion // Helpers

        #region Events

        public event EventHandler AvailableDecoderFound;

        #endregion // Events
    }
}
